package Robot

import (
	"crypto/sha1"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"log"
	"sync"
	"time"

	"../ProtoGo"
	"../TCPTerminal"

	"github.com/golang/protobuf/proto"
)

//PHLEN 包头长度
const PHLEN = 3

//XCommand 网络包数据结构
type XCommand struct {
	Cmd    byte
	Param  byte
	Probuf []byte
}

//-----------Tool-----------------
func sizeMod8(len uint32) uint32 {
	return (len + 7) / 8 * 8
}

//CalculateSHA1 计算SHA1值
func CalculateSHA1(str string) string {
	h := sha1.New()
	h.Write([]byte(str))
	bs := h.Sum(nil)
	return hex.EncodeToString(bs[:])
}

//UnpackCMD 解析网络包
func UnpackCMD(data []byte) *XCommand {
	x := XCommand{}
	x.Cmd = data[0]
	x.Param = data[1]
	x.Probuf = data[2:]
	return &x
}

//-----------Tool-----------------

type RobotAccount struct {
	Accid  uint64
	Zoneid uint32
	Addr   string
	Port   int
}

type Robot struct {
	//public
	Account RobotAccount
	Handler *RobotHandler
	//private
	recvMsg    chan []byte //接收到的消息
	sendMsg    chan []byte //准备发送的消息
	tcpClient  *TCPTerminal.TCPTerminal
	nonceTime  uint32
	nonceIndex uint32
	syc        *sync.WaitGroup //同步操作
	isWaiting  bool            //等待服务器返回
	timer      *time.Timer     //定时器
}

//NewRobot 创建一个Robot
func NewRobot(accinfo RobotAccount, syc *sync.WaitGroup) *Robot {
	r := Robot{Account: accinfo}
	r.recvMsg = make(chan []byte, 10)
	r.sendMsg = make(chan []byte, 10)
	r.tcpClient = TCPTerminal.NewTCPTerminal(r.recvMsg, r.sendMsg)
	r.syc = syc
	r.isWaiting = false
	return &r
}

//RobotLog 打印普通日志
func (r *Robot) RobotLog(format string, param ...interface{}) {
	if len(param) == 0 {
		log.Printf("|LOG|%d|%s\n", r.Account.Accid, format)
	} else {
		log.Printf("|LOG|%d|%s\n", r.Account.Accid, fmt.Sprintf(format, param))
	}

}

//RobotErr 打印错误日志
func (r *Robot) RobotErr(format string, param ...interface{}) {
	if len(param) == 0 {
		log.Printf("|ERROR|%d|%s\n", r.Account.Accid, format)
	} else {
		log.Printf("|ERROR|%d|%s\n", r.Account.Accid, fmt.Sprintf(format, param))
	}

}

//Loop 处理收到的消息,解析,转发
func (r *Robot) Loop() {
	r.RobotLog("开始执行")
	err := r.tcpClient.StartConnect(r.Account.Addr, r.Account.Port)

	if err != nil {
		r.RobotErr("连接服务器错误 %v", err)
		r.syc.Done()
		return
	}
	//开启定时器,超时时间为10秒
	r.timer = time.NewTimer(5 * time.Second)

	r.Handler.StartModule()

	for true {
		select {
		case data, ok := <-r.recvMsg:
			//如果channel关闭了或没有数据，那么ok将被置为false
			if !ok {
				r.RobotErr("recvMsg管道被关闭")
				goto FINISH
			}

			if len(data) == 0 {
				//断开连接
				r.RobotErr("与服务器断开连接")
				goto FINISH
			} else {
				r.timer.Stop()
				r.isWaiting = false
				//data -> |cmd|param|pbdata
				xcmd := UnpackCMD(data)
				r.Handler.TriggerHandle(xcmd)
			}
			if r.Handler.IsFinish() {
				//所有模块执行完成
				r.RobotLog("完成所有模块")
				//break
				goto FINISH
			}
		case <-r.timer.C:
			//r.RobotLog(fmt.Sprintf("触发计时器 %v", r.isWaiting))
			if r.isWaiting {
				r.RobotLog("等待超时")
				goto FINISH
			}
			//break
		}
	}
	// 这里地方关闭接收channel会引发错误
	// 不要从接收端关闭channel
	// close(r.recvMsg)
	//只能关闭发送channel
	//close(r.sendMsg)
FINISH:
	r.tcpClient.CloseTerminal()
	r.SendProto(0, 0, nil, false) //通过触发,关闭channel
	r.RobotLog("执行结束")
	r.syc.Done()
}

//SendProto 发送协议
//wait 表示是否需要等待服务器返回协议
//|包头(flag+packetLen)|cmd|param|NonceLen|Nonce|pb
func (r *Robot) SendProto(c, p byte, pb proto.Message, wait bool) {
	if !r.tcpClient.IsConnect() {
		close(r.sendMsg)
		return
	}
	//设置超时时间
	r.timer.Reset(5 * time.Second)
	r.isWaiting = wait
	//打印发送消息
	r.RobotLog("发送协议%s", CmdParamName(int32(c), int32(p)))

	pbBytes, err := proto.Marshal(pb)
	if err != nil {
		r.RobotErr("SendProto 序列化协议 cmd %d param %d error %v", c, p, err)
		return
	}
	nonceBytes, err := r.GenerateNonce()
	if err != nil {
		r.RobotErr("SendProto 序列化协议 Nonce")
		return
	}
	nonceLen := len(nonceBytes)
	pbLen := len(pbBytes)

	packetLen := PHLEN + 2 + 1 + 1 + nonceLen + pbLen
	netbytes := make([]byte, packetLen)

	offset := 0
	//flag
	netbytes[offset] = 0
	offset++
	//packetlen
	binary.LittleEndian.PutUint16(netbytes[offset:3], uint16(packetLen-PHLEN))
	offset += 2
	//cmd
	netbytes[offset] = c
	offset++
	//param
	netbytes[offset] = p
	offset++
	//NonceLen
	binary.LittleEndian.PutUint16(netbytes[offset:], uint16(len(nonceBytes)))
	offset += 2
	//Nonce data
	copy(netbytes[offset:], nonceBytes[:])
	offset += nonceLen
	copy(netbytes[offset:], pbBytes)

	r.sendMsg <- netbytes[:]
}

//GenerateNonce 生成固定头部协议
func (r *Robot) GenerateNonce() ([]byte, error) {
	nonce := &Cmd.Nonce{}
	now := uint32(time.Now().Unix())
	if now != r.nonceTime {
		r.nonceIndex = 0
		r.nonceTime = now
	}
	nonce.Timestamp = proto.Uint32(uint32(r.nonceTime))
	r.nonceIndex++
	nonce.Index = proto.Uint32(r.nonceIndex)
	signStr := fmt.Sprintf("%d_%d_!^ro&", nonce.GetTimestamp(), nonce.GetIndex())
	sha1SignStr := CalculateSHA1(signStr)
	nonce.Sign = proto.String(sha1SignStr)

	return proto.Marshal(nonce)
}

// CmdName 获取名字
func CmdName(cmd int32) string {
	cmdName, exist := Cmd.Command_name[cmd]
	if !exist {
		return fmt.Sprintf("(???%d???)", cmd)
	}
	return fmt.Sprintf("(%s)", cmdName)
}

//CmdParamName 获取名字
func CmdParamName(cmd, param int32) string {
	switch cmd {
	case int32(Cmd.Command_LOGIN_USER_PROTOCMD):
		cmdName := CmdName(cmd)
		paramName, exist := Cmd.LoginCmdParam_name[param]
		if !exist {
			return fmt.Sprintf("(%s)(???%d???)", cmdName, param)
		}
		return fmt.Sprintf("(%s)(%s)", cmdName, paramName)
	}
	return fmt.Sprintf("(???%d???)(???%d???)", cmd, param)
}
